﻿#include <QtCore>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlRecord>

#include "chat_message_define.h"
#include "chat_personal_define.h"

extern std::string xml2json(const char* xml_str);
namespace helper {
int   GetDatabaseQueryCount(QSqlQuery& query);
void  ReplaceEmojiMsgToImage(QString& source);
QSize GetImageSize(const QString& path);
bool  DecodeWechatImage(QString src_path, QString& dst_path);
bool  DownloadWechatImage(const QString& src_path, QString& dst_path);

QByteArray ReadDataFromFile(const QString& path);
bool       SaveContent2File(const QByteArray& data, const QString& path, bool enable_log);
}  // namespace helper

namespace {
QString findExistsPath(QString path) {
  if (QFile::exists(path + ".jpg")) {
    return path + ".jpg";
  }
  if (QFile::exists(path + ".gif")) {
    return path + ".gif";
  }
  if (QFile::exists(path + ".png")) {
    return path + ".png";
  }
  return QString();
};

int findSuffixIndex(QByteArray& extra) {
  int index = extra.indexOf(".") + 1;  // skip .
  for (int count = extra.length(); index < count; index++) {
    char c = extra.at(index);
    if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
      continue;
    } else {
      index += 1;
      break;
    }
  }
  return index;
}

QString decodeEmojis(QString        msgContent,
                     QByteArray     extra,
                     const QString& sourcePath,
                     const QString& outputPath) {
  if (!QFile::exists(sourcePath)) {
    QDir().mkdir(sourcePath);
  }

  //解析出图片md5 和 url
  QString key;
  QString cdnurl;
  if (msgContent.contains("<type>8</type>")) {
    //<emoticonmd5>0ab3eacba35f5a0d3db4ce048b4360f7</emoticonmd5>
    msgContent = msgContent.mid(msgContent.indexOf("<emoticonmd5>"));
    msgContent = msgContent.mid(msgContent.indexOf(">") + 1);
    key        = msgContent.left(msgContent.indexOf("</"));
  } else {
    std::string     jsonNode = xml2json(msgContent.toStdString().c_str());
    QJsonParseError parseJsonErr;
    QJsonDocument   document =
        QJsonDocument::fromJson(QString::fromStdString(jsonNode).toUtf8(), &parseJsonErr);
    if (!(parseJsonErr.error == QJsonParseError::NoError)) {
      qWarning() << "解析json文件错误: " << jsonNode.c_str();
      return "";
    }
    auto jsonMap = document.object().toVariantMap();
    auto value   = jsonMap["msg"].toMap()["emoji"].toMap();

    //解析出持久化在本地的名称
    QString md5        = value["@md5"].toString();
    QString androidMd5 = value["@androidmd5"].toString();
    cdnurl             = value["@cdnurl"].toString().trimmed();
    key                = androidMd5.isEmpty() ? md5 : androidMd5;
  }

  if (key.isEmpty()) {
    qWarning() << "decode emoji fail, no md5 found";
    return "";
  }

  //先尝试从缓存里面解析出
  static QMap<QString, QString> cache;
  if (cache.contains(key)) {
    return cache.value(key);
  }

  //接着检查输出目录是否已经存在，存在就无须再处理了
  QString maybeImage = findExistsPath(outputPath + key);
  if (!maybeImage.isEmpty()) {
    cache[key] = maybeImage;
    return maybeImage;
  }

  //尝试从extra里面读取
  QString orgImg = sourcePath + key;
  QString outImg = outputPath + key;
  if (!QFile::exists(orgImg) && extra.contains("Attachment")) {
    extra  = extra.mid(extra.indexOf("Attachment"));
    orgImg = extra.left(findSuffixIndex(extra) - 1);
    if (orgImg.contains("_")) {
      //这个是小图，需要重新获取
      extra       = extra.mid(orgImg.length());
      extra       = extra.mid(extra.indexOf("Attachment"));
      QString tmp = extra.left(findSuffixIndex(extra) - 1);
      if (!tmp.isEmpty()) {
        orgImg = tmp;
      }
    }
    if (!orgImg.isEmpty()) {
      //多传了一个CustomEmotions
      orgImg = sourcePath.left(sourcePath.lastIndexOf("CustomEmotions/")) + orgImg;
    }
    //有原图
    if (orgImg.endsWith(".gif") && QFile::exists(orgImg)) {
      outImg += ".gif";
      QFile::copy(orgImg, outImg);
      cache[key] = outImg;
      return outImg;
    }
  }

  //开始解密
  bool ret = false;
  if (QFile::exists(orgImg)) {
    ret = helper::DecodeWechatImage(orgImg, outImg);
    if (ret) {
      cache[key] = outImg;
      return outImg;
    }
  }

  //已经证实md5是内容的md5
  if (!cdnurl.isEmpty()) {
    //准备通过连接下载图片
    if (helper::DownloadWechatImage(cdnurl, outImg)) {
      if (!key.isEmpty()) {
        cache[key] = outImg;
      }
      return outImg;
    }

    //下载失败的处理
    qWarning() << "decode emoji fail, we has url: " << cdnurl << ", md5: " << key;
    if (!key.isEmpty()) {
      cache[key] = cdnurl;
    }
    return cdnurl;
  } else {
    //去重，找出不存在的md5，手动获取表情资源替换掉
    // qWarning() << "decode emoji fail, nothing can do, " << msgContent;
    static QSet<QString> lostEmoji;
    if (!lostEmoji.contains(key)) {
      lostEmoji.insert(key);
      qWarning() << "decode emoji fail, nothing can do, md5: " << key;
    }
  }
  return QString();
}

QString decodeImages(const QString& svrId,
                     QByteArray     extra,
                     const QString& sourcePath,
                     const QString& outputPath,
                     QString&       thumbPath) {
  if (!QFile::exists(outputPath)) {
    QDir().mkdir(outputPath);
  }

  //尝试从数据库信息里面解析2个路径
  QString orgBigImg, orgSmallImg;
  if (extra.count(".dat") >= 2) {
    //大图
    int startIndex = extra.indexOf("Data") + 5;  // 4 is length of Data + /
    int endIndex   = extra.indexOf(".dat") + 4;  // 4 is length of .dat
    orgBigImg      = extra.mid(startIndex, endIndex - startIndex);

    //缩略图
    extra       = extra.mid(endIndex);
    startIndex  = extra.indexOf("Data") + 5;
    endIndex    = extra.indexOf(".dat") + 4;
    orgSmallImg = extra.mid(startIndex, endIndex - startIndex);

    if (!orgBigImg.isEmpty()) {
      orgBigImg = sourcePath + orgBigImg;
    }
    if (!orgSmallImg.isEmpty()) {
      orgSmallImg = sourcePath + orgSmallImg;
    }

    //有可能顺序会相反，交换一下
    if (orgBigImg.contains("Tiny") && !orgSmallImg.isEmpty()) {
      orgBigImg.swap(orgSmallImg);
    }
  }

  //没有就尝试挣扎一下，尝试检查几个目录里面是否存在
  // if (!QFile::exists(orgBigImg)) {
  //  QString bigSourceImage = sourcePath + svrId + ".dat";
  //}
  if (!QFile::exists(orgSmallImg)) {
    // 缩略图在 Data/Tiny，结尾可能是_sender 或者_t
    static auto findThumbnail = [](QString path) {
      if (QFile::exists(path + "_sender.dat")) {
        return path + "_sender.dat";
      }
      if (QFile::exists(path + "_t.dat")) {
        return path + "_t.dat";
      }
      return QString();
    };
    orgSmallImg = findThumbnail(sourcePath + "/Tiny/" + svrId);
  }

  //解密大图。如果已经解密过的话可以跳过
  bool    ret         = false;
  QString outImg      = outputPath + svrId;
  QString cacheBigImg = findExistsPath(outImg);
  if (!cacheBigImg.isEmpty()) {
    // good
  } else if (QFile::exists(orgBigImg)) {
    ret = helper::DecodeWechatImage(orgBigImg, outImg);
    //成功的话，缩略图默认也用这个，因为可能存在缩略图没有的情况
    if (ret) {
      thumbPath   = outImg;
      cacheBigImg = outImg;
    } else {
      qWarning() << "decode image fail, svrId " << svrId;
    }
  } else if (orgSmallImg.isEmpty()) {
    //啥都没有，需要警告一下
    qWarning() << "big image not exist, " << orgBigImg;
  }

  //解密小图
  outImg                = outputPath + svrId + "_thumb";
  QString cacheSmallImg = findExistsPath(outImg);
  if (!cacheSmallImg.isEmpty()) {
    // good
  } else if (QFile::exists(orgSmallImg)) {
    ret = helper::DecodeWechatImage(orgSmallImg, outImg);
    //成功的话，缩略图默认就用这个
    if (ret) {
      cacheSmallImg = outImg;
    } else {
      qWarning() << "decode thumb image fail, svrId " << svrId;
    }
  } else /*if (orgSmallImg.isEmpty())*/ {
    //没有大图也没有小图，需要警告一下
    qWarning() << "small image not exist, " << orgSmallImg;
  }

  //处理一些特殊情况，比如有缩略图但是没大图
  if (!cacheSmallImg.isEmpty()) {
    thumbPath = cacheSmallImg;
  }
  if (cacheBigImg.isEmpty()) {
    cacheBigImg = cacheSmallImg;
  }
  return cacheBigImg;
}

int parseVoiceTime(QString& msgContent) {
  int     voiceTime = 1;
  QString prefix    = "voicelength=\"";
  int     index     = msgContent.indexOf(prefix);
  if (index > -1) {
    auto rightStr = msgContent.mid(index + prefix.length());
    int  length   = rightStr.indexOf("\"");
    if (length > -1) {
      int millSeconds = rightStr.leftRef(length).toInt();
      if (millSeconds > 0) {
        voiceTime = (millSeconds + 999) / 1000;
      }
    }
    // qWarning() << "从xml解析出语音消息长度为 " << voiceTime << "s";
  }
  return voiceTime;
}

QString decodeVideos(const QString& svrId,
                     QByteArray     extra,
                     const QString& sourcePath,
                     const QString& outputPath,
                     QString&       thumbPath) {
  if (!QFile::exists(outputPath)) {
    QDir().mkdir(outputPath);
  }

  QString orgThumb, orgVideo;
  if (extra.count("Video") >= 2) {
    //缩略图
    int startIndex = extra.indexOf("Video") + 6;  // 6 is length of Data + /
    extra          = extra.mid(startIndex);
    orgThumb       = extra.left(extra.indexOf(".") + 4);  // 4 is .mp4 or .jpg

    //视频
    extra      = extra.mid(orgThumb.length());
    startIndex = extra.indexOf("Video") + 6;
    extra      = extra.mid(startIndex);
    orgVideo   = extra.left(extra.indexOf(".") + 4);

    if (!orgThumb.isEmpty()) {
      orgThumb = sourcePath + orgThumb;
    }
    if (!orgVideo.isEmpty()) {
      orgVideo = sourcePath + orgVideo;
    }

    //有可能顺序会相反，交换一下
    if (orgThumb.contains(".mp4") && !orgVideo.isEmpty()) {
      orgThumb.swap(orgVideo);
    }
  }

  QString md5          = QCryptographicHash::hash(svrId.toUtf8(), QCryptographicHash::Md5).toHex();
  QString outputPrefix = outputPath + md5;

  //挣扎一下
  if (orgThumb.isEmpty()) {
    orgThumb = sourcePath + md5 + ".jpg";
  }
  if (orgVideo.isEmpty()) {
    orgVideo = sourcePath + md5 + ".mp4";
  }

  //拷贝缩略图
  QString cachedThumb = outputPrefix + ".jpg";
  if (QFile::exists(cachedThumb)) {
    thumbPath = cachedThumb;
  } else if (QFile::exists(orgThumb)) {
    QFile::copy(orgThumb, cachedThumb);
    thumbPath = cachedThumb;
  } else {
    qWarning() << "video thumbnail not exist, svrId " << svrId;
  }

  //拷贝视频
  QString cachedVideo = outputPrefix + ".mp4";
  if (QFile::exists(cachedVideo)) {
    // thumbPath = cachedThumb;
  } else if (QFile::exists(orgVideo)) {
    QFile::copy(orgVideo, cachedVideo);
  } else {
    cachedVideo = "";
    qWarning() << "video mp4 not exist, svrId " << svrId;
  }

  return cachedVideo;
}

QString decodeFiles(QByteArray extra, const QString& sourcePath, const QString& outputPath) {
  if (!QFile::exists(outputPath)) {
    QDir().mkdir(outputPath);
  }

  QString orgFile;
  if (extra.contains(":")) {
    //例如存在下载路径，旧版
    extra   = extra.mid(extra.indexOf(":") - 1);
    orgFile = extra.left(findSuffixIndex(extra) - 1);
  } else if (extra.contains("Attachment")) {
    //例如只有相对路径的情况，新版
    extra   = extra.mid(extra.indexOf("Attachment"));
    orgFile = extra.left(findSuffixIndex(extra) - 1);

    orgFile = sourcePath + orgFile;
  }
  if (orgFile.isEmpty()) {
    return "";
  }
  orgFile.replace("\\", "/");

  QString outputFile = outputPath;
  outputFile += orgFile.midRef(orgFile.lastIndexOf("/") + 1);
  if (QFile::exists(outputFile)) {
    return outputFile;
  } else if (QFile::exists(orgFile)) {
    QFile::copy(orgFile, outputFile);
    // qWarning() << "copy file: " << orgFile << " to " << outputFile;
    return outputFile;
  } else {
    qWarning() << "parse file path: " << orgFile << ", but not exists";
  }

  return QString();
}

}  // namespace

namespace wechat {

//支持的是2016年前的微信聊天数据库
void DecodeChatMessage() {
  QString sourcePath = TARGET_SOURCE_PATH;
  QString outputPath = TARGET_OUTPUT_PATH;

  // https://blog.csdn.net/lms1008611/article/details/81271712
  QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
  db.setDatabaseName(sourcePath + R"(Msg\dec_ChatMsg.db)");
  if (!db.open()) {
    qWarning() << "open database failed";
    return;
  }

  //查询指定用户的聊天数据，并按照本地数据库id从小到大排序
  QSqlQuery query(db);
  query.prepare("SELECT * FROM ChatMsg WHERE strTalker='" TARGET_USER "' ORDER BY localId");
  query.exec();

  //统计数目
  int result_count = helper::GetDatabaseQueryCount(query);
  qDebug() << "find " << result_count << " messages";

  //获得结果
  QJsonArray jsonArray;
  while (query.next()) {
    auto localId    = query.value("localId").toInt();
    auto type       = query.value("type").toInt();
    auto isSender   = query.value("IsSender").toInt() > 0;
    auto msgSvrID   = query.value("MsgSvrID").toString();
    auto createTime = query.value("CreateTime").toLongLong();
    auto msgContent = query.value("strContent").toString();
    auto bytesExtra = query.value("bytesExtra").toByteArray();
    // qDebug() << "type:"<< type << "str: " << msgContent;

    QJsonObject item;
    item["m_uiMesLocalID"]  = localId;
    item["m_uiMesSvrID"]    = msgSvrID;
    item["m_uiCreateTime"]  = createTime;
    item["m_uiMessageType"] = type;
    item["m_nsFromUsr"]     = isSender ? "" : TARGET_USER;
    item["m_nsToUsr"]       = isSender ? TARGET_USER : "";
    item["m_nsContent"]     = msgContent;

    if (type == ChatType_Text) {
      //普通消息
      helper::ReplaceEmojiMsgToImage(msgContent);
      item["m_nsContent"] = msgContent;
    } else if (type == ChatType_Image) {
      // 图片消息
      QString thumbPath;
      QString imagePath = decodeImages(
          msgSvrID, bytesExtra, sourcePath + "Data/", outputPath + "images/", thumbPath);
      if (imagePath.isEmpty()) {
        item["m_nsContent"]   = "";
        item["m_nsThumbnail"] = "";
        item["m_nsOriginal"]  = msgContent;
        // 占位的大小
        item["m_nsWidth"]  = 180;
        item["m_nsHeight"] = 180;
      } else {
        item["m_nsContent"]   = imagePath.mid(outputPath.length());
        item["m_nsThumbnail"] = thumbPath.mid(outputPath.length());
        item["m_nsOriginal"]  = "";
        auto&& size           = helper::GetImageSize(imagePath);
        item["m_nsWidth"]     = size.width();
        item["m_nsHeight"]    = size.height();
      }
    } else if (type == ChatType_Emotion) {
      // emoji表情
      QString emojiPath = decodeEmojis(
          msgContent, bytesExtra, sourcePath + "CustomEmotions/", outputPath + "emotions/");
      if (emojiPath.isEmpty()) {
        item["m_nsContent"]  = "";
        item["m_nsOriginal"] = msgContent;
      } else {
        item["m_nsContent"]  = emojiPath.mid(outputPath.length());
        item["m_nsOriginal"] = "";  //已经解析出来就可以丢弃了
      }
    } else if (type == ChatType_Voice) {
      //语音消息
      //暂时不清楚旧版语音消息的格式如何，因为以前的PC版没有存放
      item["m_uiVoiceTime"] = parseVoiceTime(msgContent);
      item["m_nsContent"]   = "";
      item["m_nsOriginal"]  = msgContent;
    } else if (type == ChatType_Video || type == ChatType_ShortVideo) {
      //视频，key为msgSvrID的md5
      //短视频，可以从extra里面解密到信息
      QString thumbPath;
      QString videoPath = decodeVideos(
          msgSvrID, bytesExtra, sourcePath + "Video/", outputPath + "Videos/", thumbPath);
      if (videoPath.isEmpty()) {
        item["m_nsContent"]   = "";
        item["m_nsThumbnail"] = "";
        item["m_nsOriginal"]  = msgContent;
      } else {
        item["m_nsContent"]   = videoPath.mid(outputPath.length());
        item["m_nsThumbnail"] = thumbPath.mid(outputPath.length());
        item["m_nsOriginal"]  = "";
      }
    } else if (type == ChatType_Extend) {
      //扩展消息
      msgContent.replace("&#x20;", " ");
      msgContent.replace("&#x0A;", "\n");
      msgContent.replace("&#x0D;", "\r");

      //去掉xml之间的特殊换行符
      if (msgContent.startsWith("<msg>")) {
        msgContent.replace(QRegExp(">\\s+<"), "><");
      }
      if (msgContent.endsWith("</msg>\n")) {
        msgContent.chop(1);
      }

      item["m_nsContent"] = msgContent;
      if (msgContent.contains("<type>6</type>")) {
        auto filePath         = decodeFiles(bytesExtra, sourcePath, outputPath + "attachment/");
        item["m_nsFilePath"]  = filePath.mid(outputPath.length());
        item["m_nsFileExist"] = !filePath.isEmpty();
      } else if (msgContent.contains("<type>8</type>")) {
        //表情消息，尝试解析
        QString emojiPath = decodeEmojis(
            msgContent, bytesExtra, sourcePath + "CustomEmotions/", outputPath + "emotions/");
        if (!emojiPath.isEmpty()) {
          item["m_uiMessageType"] = 47;  //重新转换为表情类型
          item["m_nsContent"]     = emojiPath.mid(outputPath.length());
          item["m_nsOriginal"]    = "";
        }
      }
      // else if (!msgContent.contains("<type>5</type>") && !msgContent.contains("<type>3</type>"))
      // {
      //    qWarning() << msgContent;
      //}
    }
    jsonArray.push_back(item);
  }

  //格式化json
  QString content = QJsonDocument(jsonArray).toJson(QJsonDocument::Indented);

  //读取模板信息并写入到新的文件
  QString bytes = helper::ReadDataFromFile(outputPath + "js/message_tmp.js");
  helper::SaveContent2File(bytes.arg(content).toUtf8(), outputPath + "js/message.js", true);
}

}  // namespace wechat
